package cn.com.cetccst.skf;

import static com.westone.skf.SkfDefines.USER_TYPE;

import android.content.Context;
import android.util.Log;

import com.westone.skf.DEVHANDLE;
import com.westone.skf.ECCCIPHERBLOB;
import com.westone.skf.HANDLE;
import com.westone.skf.HAPPLICATION;
import com.westone.skf.HCONTAINER;
import com.westone.skf.PinRetryCount;
import com.westone.skf.SKFException;
import com.westone.skf.SkfDefines;
import com.westone.skf.SkfWrapper;

import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import cn.com.cetccst.onlinecert.MdmUserInfo;
import cn.com.cetccst.onlinecert.OnlineCertManager;
import cn.com.cetccst.onlinecert.R;

public class SkfEntry implements ISkfEntry {
    private static final String TAG = "SkfEntry";

    private static final String DEV_TYPE_SOFT_CARD = "WST_SCML1";//一级软卡
    private static final String DEV_TYPE_SOFT_CARD2 = "WST_SCML2";//二级软卡
    private static final String DEV_TYPE_TF_JW = "WST_HSTFJW";//嘉微TF卡
    private static final String DEV_TYPE_TF_HD = "WST_LSTFHD";//华大TF卡
    private static final String DEV_TYPE_SKF = "01234";//贴芯卡
    private static final String DEV_TYPE_TF_TSLD = "WSTYH_JAVACARD"; //太思隆达2代
    private static final String DEV_TYPE_NONE = "NO_DEVICE";//本地参数，代表无卡

    private String MY_DEV_NAME = DEV_TYPE_SOFT_CARD;
    private String MY_APP_NAME = "WStUsbKey";
    private String MY_CONTAINER_NAME = "ExchContainer";
    private String DEV_KEY = "1234567812345678";
    private String INIT_ADMIN_PIN = "88888888";   //应用容器初始管理员PIN码
    private long ADMIN_RETRY_COUNT = 6;           //管理员PIN码锁死次数
    private String INIT_USER_PIN = "11111111";      //应用容器初始管理员PIN码
    private long USER_RETRY_COUNT = 6;            //用户PIN码锁死次数
    private String clientId = "wst";
    private String appId = "18c0e4b323324ad383ae0c37bea576a5";
    private String appSecret = "944AEFECD514D939DBE5F9113D7A2412";
    private String token = " 05715ed9-406f-4b82-bbbb-de6fe471c198";

    private DEVHANDLE mDevHandle = new DEVHANDLE();
    private HAPPLICATION mAppHandle = new HAPPLICATION();
    private HCONTAINER mContainer = new HCONTAINER();

    private Context mContext;
    private SkfWrapper mSkfWrapper;
    private P10CSRGenerator mP10CSRGenerator;
    private boolean isReady = false;
    private boolean useL2Softcard = true;
    private byte[] mSignPublic;
    private byte[] mSignCert;

    public SkfEntry(Context context) {
        mContext = context;
        mP10CSRGenerator = new P10CSRGenerator();
        initWrapper();
        try {
            mSkfWrapper = SkfWrapper.getInstance();
            if (mSkfWrapper == null) {
                Log.e(TAG, "skfWrapper is null ");
                return;
            }
            disableCard();
            if (isOnlineCertEnabled()) {
                initSoftCard(context);
            } else {
                isReady = true;
            }
        } catch (SKFException e) {
            Log.e(TAG, "skfWrapper getInstance failed. error: ", e);
        }

    }

    private void initWrapper() {
        String logDir = mContext.getExternalFilesDir(null) + File.separator + "logs";
        try {
            File dir = new File(logDir);
            if (!dir.exists()) {
                dir.mkdir();
            }
            SkfWrapper.getInstance(SkfWrapper.CMLOG_DEFAULT, logDir);
        } catch (Exception e) {
            Log.e(TAG, "failed to init skf wrapper", e);
        }
    }

    public void initSoftCard(Context context) {
        String scs_level = context.getString(R.string.scs_level);
        Log.i(TAG, "scs skf sdk level " + scs_level);
        useL2Softcard = !scs_level.equals("1");
        try {
            MY_DEV_NAME = useL2Softcard ? DEV_TYPE_SOFT_CARD2 : DEV_TYPE_SOFT_CARD;
            List<String> szNameList = enumDev();
            if (szNameList.contains(MY_DEV_NAME)) {
                mSkfWrapper.SKF_ConnectDev(MY_DEV_NAME, mDevHandle);
                Log.i(TAG, "SKF_ConnectDev success, devName: " + MY_DEV_NAME);
                initApplication();
            } else if (useL2Softcard) {
                //二级软卡需要手动生产
                //为了编译兼容一级软卡，此函数使用反射调用
                Class<?> myClass = mSkfWrapper.getClass();
                Method method = myClass.getMethod("CmSkfBuildSoftCardL2", String.class, String.class, String.class, String.class, String.class);
//                skfWrapper.CmSkfBuildSoftCardL2(clientId, appId, appSecret, token, "cs_config.ini");
                try {
                    method.invoke(mSkfWrapper, clientId, appId, appSecret, token, "cs_config.ini");
                } catch (Exception e) {
                    Log.e(TAG, "SKF_ConnectDev origin exception: ", e);
                    //获取反射调用异常的根异常
                    Throwable root = getRootException(e);
                    if (root instanceof SKFException) {
                        throw (SKFException) root;
                    } else if (root instanceof ReflectiveOperationException) {
                        throw (ReflectiveOperationException) root;
                    } else {
                        throw e;
                    }
                }

                szNameList = enumDev();
                if (szNameList.contains(MY_DEV_NAME)) {
                    mSkfWrapper.SKF_ConnectDev(MY_DEV_NAME, mDevHandle);
                    Log.i(TAG, "SKF_ConnectDev success, devName: " + MY_DEV_NAME);
                    initApplication();
                }
            }
        } catch (SKFException e) {
            Log.e(TAG, "SKF_ConnectDev error: " + e.getError(), e);
        } catch (ReflectiveOperationException e) {
            Log.e(TAG, "SKF_ConnectDev reflect error: ", e);
        } catch (Exception e) {
            Log.e(TAG, "SKF_ConnectDev other error: ", e);
        }
    }

    private Throwable getRootException(Exception e) {
        Throwable root = e;
        while (root.getCause() != null) {
            root = root.getCause();
        }
        return root;
    }

    public void initApplication() {
        Log.i(TAG, "initApplication IN");
        if (mDevHandle == null) {
            Log.e(TAG, "initApplication handle null");
            return;
        }
        List<String> appNameList = new ArrayList();
        try {
            mSkfWrapper.SKF_EnumApplication(mDevHandle, appNameList);
            Log.i(TAG, String.valueOf(appNameList));
            for (String appName : appNameList) {
                if (appName.equals(MY_APP_NAME)) {
                    mSkfWrapper.SKF_OpenApplication(mDevHandle, appName, mAppHandle);
                    Log.i(TAG, "SKF_OpenApplication success, appName: " + appName);
                    loginCard();
                    return;
                }
            }

            devAuth(mDevHandle, DEV_KEY);
            Log.i(TAG, "devAuth success");
            mSkfWrapper.SKF_CreateApplication(mDevHandle, MY_APP_NAME, INIT_ADMIN_PIN, ADMIN_RETRY_COUNT, INIT_USER_PIN, USER_RETRY_COUNT, 0, mAppHandle);
            Log.i(TAG, "SKF_CreateApplication success");
            loginCard();
        } catch (SKFException e) {
            Log.e(TAG, "initApplication error: ", e);
        }
    }

    private void devAuth(DEVHANDLE devHandle, String devKey) throws SKFException {
        final long SGD_SMS4_ECB = 0x401;
        mSkfWrapper = SkfWrapper.getInstance();
        byte[] rnd = mSkfWrapper.SKF_GenRandom(devHandle, 8);
        byte[] auth = Arrays.copyOf(rnd, 16);
        long feedBitLen = 128;     //16*8

        HANDLE handle = new HANDLE();
        mSkfWrapper.SKF_SetSymmKey(devHandle, devKey.getBytes(), SGD_SMS4_ECB, handle);
        mSkfWrapper.SKF_EncryptInit(handle, null, 0, feedBitLen);
        byte[] cipher = mSkfWrapper.SKF_Encrypt(handle, auth);
        mSkfWrapper.SKF_CloseHandle(handle);
        mSkfWrapper.SKF_DevAuth(devHandle, cipher);
    }

    public void loginCard() {
        Log.i(TAG, "loginCard start");
        if (mAppHandle == null) {
            Log.e(TAG, "app handle null");
            return;
        }
        PinRetryCount retryCount = new PinRetryCount();
        try {
            mSkfWrapper.SKF_VerifyPIN(mAppHandle, USER_TYPE, INIT_USER_PIN, retryCount);
            Log.i(TAG, "login success! getRetryCount: " + retryCount.getRetryCount());
            initContainer();
        } catch (SKFException e) {
            Log.e(TAG, "login error! getRetryCount: " + retryCount.getRetryCount());
        }
    }

    private void initContainer() {
        try {
            List<String> containerNameList = new ArrayList<>();
            mSkfWrapper.SKF_EnumContainer(mAppHandle, containerNameList);
            Log.i(TAG, "initContainer, containerList: " + String.valueOf(containerNameList));

            for (String appName : containerNameList) {
                if (appName.equals(MY_CONTAINER_NAME)) {
                    mSkfWrapper.SKF_OpenContainer(mAppHandle, appName, mContainer);
                    Log.i(TAG, "SKF_OpenContainer success, containerName: " + appName);
                    handleContainer();
                    return;
                }
            }
            mSkfWrapper.SKF_CreateContainer(mAppHandle, MY_CONTAINER_NAME, mContainer);
            Log.i(TAG, "SKF_CreateContainer success");
            handleContainer();
        } catch (Exception e) {
            Log.e(TAG, "initContainer error: ");
        }
    }

    private void handleContainer() {
        try {
            mSignPublic = mSkfWrapper.SKF_ExportPublicKey(mContainer, true);//导出签名公钥

            if (mSignPublic != null) {
                Log.i(TAG, "initKeyPair 签名公钥hex: " + Utils.byteToHex(mSignPublic));
            } else {
                mSignPublic = mSkfWrapper.SKF_GenECCKeyPair(mContainer, SkfDefines.SGD_SM2_1);
                Log.i(TAG, "SKF_GenECCKeyPair success: " + Utils.byteToHex(mSignPublic));
            }
            isReady = true;
        } catch (Exception e) {
            Log.e(TAG, "handleContainer : " + e.getMessage() + ",Error = " + String.format("0x%08x", SKFException.getLastError()));
        }
    }

    private void disableCard() {
        try {
            Log.i(TAG, "disableCard start ");
            SkfWrapper.DisableDev(getDisableList());
            Log.i(TAG, "disableCard success.");
            enumDev();
        } catch (SKFException e) {
            Log.e(TAG, "disableCard error: ", e);
        }
    }

    private List<String> enumDev() {
        List<String> szNameList = new ArrayList();
        try {
            mSkfWrapper.SKF_EnumDev(szNameList);
            Log.i(TAG, "SKF_EnumDev : " + String.valueOf(szNameList));
        } catch (SKFException e) {
            Log.e(TAG, "enumDev error: ", e);
        }
        return szNameList;
    }

    private List<String> getDisableList() {
        List<String> list = new ArrayList();
        if (isOnlineCertEnabled()) {
//            list = enumDev();
//            list.remove(DEV_TYPE_SOFT_CARD);
            //EMC-914
            list.add(DEV_TYPE_TF_HD);
            list.add(DEV_TYPE_SKF);
            list.add(DEV_TYPE_TF_TSLD);
        } else {
            list.add(DEV_TYPE_SOFT_CARD);
            list.add(DEV_TYPE_SOFT_CARD2);
        }
        return list;
    }

    private boolean isOnlineCertEnabled() {
        return OnlineCertManager.getInstance().isOnlineCertEnabled();
    }

    @Override
    public byte[] getPublicKey(boolean sign) {
        byte[] pk = sign ? mSignPublic : null;
        if (pk != null) {
            return pk;
        } else {
            Log.i(TAG, "public key  is null, type:  " + (sign ? "sign" : "encrypt"));
            return null;
        }
    }

    @Override
    public byte[] sign(byte[] content) {
        try {
            HANDLE handle = new HANDLE();
            mSkfWrapper.SKF_DigestInit(mDevHandle, SkfDefines.SGD_SM3, mSignPublic, "1234567812345678".getBytes(), handle);
            byte[] result = mSkfWrapper.SKF_ECCSignData(mContainer, mSkfWrapper.SKF_Digest(handle, content));
            mSkfWrapper.SKF_CloseHandle(handle);
            return result;
        } catch (SKFException e) {
            Log.e(TAG, "signECC : " + e.getMessage() + ",Error = " + String.format("0x%08x", SKFException.getLastError()));
        }
        return null;
    }

    @Override
    public boolean importCert(byte[] cert, boolean sign) {
        try {
            mSkfWrapper.SKF_ImportCertificate(mContainer, sign, cert);
            Log.i(TAG, "importCert success type :" + (sign ? "sign" : "encrypt"));
            return true;
        } catch (SKFException e) {
            Log.e(TAG, "importCert : " + e.getMessage() + ",Error = " + String.format("0x%08x", SKFException.getLastError()));
        }
        return false;
    }

    @Override
    public void importKeyPair(byte[] workKeyByte) {

        int headerLength = 12;
        int start = headerLength + 32;

        byte[] length = new byte[4];
        System.arraycopy(workKeyByte, 8, length, 0, length.length);

        byte[] cbEncryptedPriKey = new byte[32];
        System.arraycopy(workKeyByte, start, cbEncryptedPriKey, 0, cbEncryptedPriKey.length);

        start += 4 + cbEncryptedPriKey.length + 32;
        byte[] pubKeyX = new byte[32];
        System.arraycopy(workKeyByte, start, pubKeyX, 0, pubKeyX.length);

        start += pubKeyX.length + 32;
        byte[] pubKeyY = new byte[32];
        System.arraycopy(workKeyByte, start, pubKeyY, 0, pubKeyY.length);

        start += pubKeyY.length;
        byte[] blob = new byte[workKeyByte.length - start];
        System.arraycopy(workKeyByte, start, blob, 0, blob.length);

        byte[] eccPubKey = new byte[64];
        System.arraycopy(pubKeyX, 0, eccPubKey, 0, pubKeyX.length);
        System.arraycopy(pubKeyY, 0, eccPubKey, 32, pubKeyY.length);

        try {
            ECCCIPHERBLOB ecccipherblob = Utils.byteToECCCIPHERBLOB(blob);
            mSkfWrapper.SKF_ImportECCKeyPair(mContainer, cbEncryptedPriKey, eccPubKey, ecccipherblob);
        } catch (SKFException e) {
            Log.e(TAG, "importEncryptKeypair : " + e.getMessage() + ",Error = " + SKFException.getLastError());
        }
    }

    @Override
    public byte[] genPKCS10(MdmUserInfo userInfo) {
        return mP10CSRGenerator.generatePKCS10(this, userInfo);
    }

    @Override
    public void destory() {
        try {
            mSkfWrapper.SKF_DeleteContainer(mAppHandle, MY_CONTAINER_NAME);
            Log.i(TAG, "destroy container success");
        } catch (SKFException e) {
            Log.e(TAG, "destroy : " + e.getMessage() + ",Error = " + String.format("0x%08x", SKFException.getLastError()));
        }
    }

    @Override
    public boolean isReady() {
        return isReady;
    }
}
